package logger

import (
	"fmt"
	"os"
	"strings"
	"sync"
	"time"
)

type TargetFileConfig struct {
	Filename string
	Ext      string

	RotatePerSizeKB     int64
	RotatePerTimeMinute int64

	rotatePerSize int64
	rotatePerSec  int64
}

func (cfg *TargetFileConfig) FixDefault() {
	if cfg.Filename == "" {
		cfg.Filename = "all"
	}
	if cfg.Ext == "" {
		cfg.Ext = ".log"
	}
	cfg.Filename = strings.TrimSuffix(cfg.Filename, cfg.Ext)

	cfg.rotatePerSize = cfg.RotatePerSizeKB * 1024
	cfg.rotatePerSec = cfg.RotatePerTimeMinute * 60
}

type TargetFile struct {
	cfg *TargetFileConfig

	Level Level
	File  *os.File

	baseFilepath string

	rotateFunc      func(now time.Time, size int)
	rotateLock      sync.Mutex
	rotateNextTime  int64
	filenameTime    int64
	rotateIndex     int
	rotateStackSize int64
}

func NewTargetFile(_cfg *TargetFileConfig) (*TargetFile, error) {
	var cfg TargetFileConfig
	if _cfg == nil {
		cfg = TargetFileConfig{}
	} else {
		cfg = *_cfg
	}
	cfg.FixDefault()

	t := &TargetFile{
		cfg:          &cfg,
		Level:        LevelDebug,
		baseFilepath: cfg.Filename + cfg.Ext,
	}

	if t.cfg.RotatePerSizeKB > 0 || t.cfg.RotatePerTimeMinute > 0 {
		now := time.Now()
		t.initialRotateNextTime(now)
		t.filenameTime = t.tsAlignMinuteEnd(now)
		t.testExistFileToRotateIndex()

		t.rotateFunc = t.rotate
	} else {
		t.rotateFunc = t.noRotate
	}

	f, err := os.OpenFile(t.baseFilepath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
	if err != nil {
		return nil, fmt.Errorf("open file %s: %v", t.baseFilepath, err)
	}
	t.File = f

	return t, nil
}

func (t *TargetFile) IgnoreCheck(ctx *Context) bool {
	return ctx.Level() < t.Level
}

func (t *TargetFile) Print(ctx *Context) {
	t.rotateLock.Lock()
	t.rotateFunc(ctx.Now, ctx.Buffer().Len())
	t.rotateLock.Unlock()

	t.File.Write(ctx.Buffer().Bytes())
	t.File.Sync()
}

func (t *TargetFile) Close() error {
	return t.File.Close()
}

func (t *TargetFile) noRotate(now time.Time, size int) {}

func (t *TargetFile) rotate(now time.Time, size int) {
	nowTs := now.Unix()
	t.rotateStackSize += int64(size)
	if t.rotateStackSize < t.cfg.rotatePerSize && nowTs < t.rotateNextTime {
		//不用rotate
		return
	}

	if nowTs >= t.rotateNextTime {
		t.refreshRotateNextTime(now)
	}

	//计算文件名时间
	useTs := t.tsAlignMinuteEnd(now)
	if useTs > t.filenameTime {
		t.filenameTime = useTs
		t.rotateIndex = 0 //文件名时间有变更时重置rotateIndex
	} else {
		t.rotateIndex++
	}

	//关闭当前文件
	t.File.Sync()
	if err := t.File.Close(); err != nil {
		fmt.Fprintf(os.Stderr, "Error close file %s: %v\n", t.baseFilepath, err)
		return
	}

	//对当前文件重命名
	rfp := fmt.Sprintf("%s.%s.%d%s", t.cfg.Filename, time.Unix(useTs, 0).Format("20060102.1504"), t.rotateIndex, t.cfg.Ext)
	if err := os.Rename(t.baseFilepath, rfp); err != nil {
		fmt.Fprintf(os.Stderr, "Error rename file %s to %s: %v\n", t.baseFilepath, rfp, err)
		t.File = os.Stderr //防止无文件可写
		return
	}
	t.rotateStackSize = 0

	//打开新文件
	f, err := os.OpenFile(t.baseFilepath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Error open file %s: %v\n", t.baseFilepath, err)
		t.File = os.Stderr //防止无文件可写
		return
	} else {
		t.File = f
	}
}

func (t *TargetFile) testExistFileToRotateIndex() {
	testTimeStr := time.Unix(t.filenameTime, 0).Format("20060102.1504")

	t.rotateIndex = -1
	for i := 0; i < 1000; i++ {
		rfp := fmt.Sprintf("%s.%s.%d%s", t.cfg.Filename, testTimeStr, i, t.cfg.Ext)
		_, err := os.Stat(rfp)
		if err != nil && os.IsNotExist(err) {
			break
		}
		t.rotateIndex = i
	}
}

func (t *TargetFile) tsAlignMinuteEnd(now time.Time) int64 {
	ts := now.Unix() - int64(now.Second())
	if now.Second() > 0 {
		ts += 60
	}
	return ts
}

func (t *TargetFile) initialRotateNextTime(now time.Time) {
	baseTime := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
	t.rotateNextTime = baseTime.Unix()
	t.refreshRotateNextTime(now)
}

func (t *TargetFile) refreshRotateNextTime(now time.Time) {
	t.rotateNextTime += ((now.Unix()-t.rotateNextTime)/(t.cfg.rotatePerSec) + 1) * (t.cfg.rotatePerSec)
}
